﻿using System;
using System.Linq;

namespace Dpy.Math.Algebra
{
    /// <summary>
    /// Solves linear equations systems of type AX=B for squared matrices A
    /// </summary>
    public class LinearEquationsSystem
    {
        private Matrix A { get; set; }
        private Matrix B { get; set; }

        public bool HasUniqueSolution { get; private set; }
        public bool IsSolvable { get; private set; }
        public Vector Solution { get; private set; } 

        public LinearEquationsSystem(double[,] variablesCoeficients, double[] freeCoeficients)
            : this(new Matrix(variablesCoeficients), new Vector(freeCoeficients))
        {
        }

        public LinearEquationsSystem(Matrix variableCoeficients, Vector freeCoeficients)
            : this(variableCoeficients, freeCoeficients.ToColumnMatrix())
        {
        }

        public LinearEquationsSystem(Matrix variableCoeficients, Matrix freeCoeficients)
        {
            this.A = variableCoeficients;
            this.B = freeCoeficients;

            if (A.Columns != A.Rows)
            {
                throw new ArgumentException("You could solve only linear systems with square matrixes only!");
            }

            if (A.Rows != B.Rows)
            {
                throw new ArgumentException("The number of free coeficients must equal the numbers of equations!");
            }

            if (B.Columns > 1)
            {
                throw new ArgumentException("Free coeficients must be matrix with only one column!");
            }

            Vector result = null;
            this.IsSolvable = TrySolve(out result);
            this.Solution = result;
            this.HasUniqueSolution = this.IsSolvable && this.Solution != null;            
        }

        private bool TrySolve(out Vector result)
        {
            result = null;

            this.B.AttachToAnotherMatrixEvents(this.A);

            this.A.TrianglulateRowsBelowMainDiagonal();

            bool hasInverse = true;

            for (int i = 0; i < this.A.Rows; i++)
            {
                if (this.A[i, i].IsZero())
                {
                    if (this.A.IsZeroRow(i) && !this.B[i, 0].IsZero())
                    {
                        return false;
                    }

                    hasInverse = false;
                    continue;
                }

                this.A.MultiplyRowByNumber(i, 1 / this.A[i, i]);
            }

            if (hasInverse)
            {
                this.A.TriangulateRowsAboveMainDiagonal();
                result = this.B.ColumnVector(0);
            }

            this.B.DetachFromAnotherMatrixEvents(this.A);

            return true;
        }
    }
}
